package org.groovymud.scripting.domain;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.util.*;

import javax.script.Bindings;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.log4j.Logger;
import org.groovymud.config.MudConfig;
import org.groovymud.object.MudObject;
import org.groovymud.scripting.utils.BindingDelegate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

/**
 * used to process inline scripts that contain object definitions
 * 
 * does not use the application context, athough is aware of it so
 * it can pass it to scripts as a binding.
 *  
 *  Scripts should be of the format:
 *  {@code}
 *  import blah
 *  
 *  .. script content, class definitions etc..
 *  
 *  [objectUniqueName: new MyMudObject(x:1, y:2)]
 *  {@code}
 *  
 *  the map will be returned to the domain manager, and you can the call cloneObject on 
 *  it to get a copy of the object it's self.
 *  
 * @author corbym
 *
 */
public class ScriptedDomainManager extends AbstractSuffixedScriptedDomainManager<MudObject> {

	private static final Logger logger = Logger.getLogger(BeanBuilderDomainManager.class);

	Map<String, Object> synchronizedMap = Collections.synchronizedMap(new TreeMap<String, Object>());
    private final MudConfig mudConfig;

    /**
	 * the prefix to match for before the suffixe, eg for *Script.js, the prefix would be Script
	 */

    public ScriptedDomainManager(BindingDelegate bindingDelegate, MudConfig mudConfig, String suffixPrefix) {
        super(bindingDelegate, domainSuffixe(suffixPrefix));
        this.mudConfig = mudConfig;
    }

    public List<String> getHandles() {
		return new ArrayList<String>(synchronizedMap.keySet());
	}
	/** 
	 * loads all the script extensions the scriptEngines are capable of running. prefixes the suffixe with
	 * a value specified by suffixePrefix so we only load scripts we need
     * @param suffixePrefix
     */
	public static String[] domainSuffixe(String suffixePrefix) {
		final ScriptEngineManager manager = new ScriptEngineManager();
		List<String> suffixe = new ArrayList<String>();
		for (final ScriptEngineFactory scriptEngine : manager.getEngineFactories()) {
			for (final String ext : scriptEngine.getExtensions()) {
				suffixe.add(suffixePrefix + "." + ext);
			}
		}
        return suffixe.toArray(new String[suffixe.size()]);
	}

	public boolean canLoad(String scriptLocation) {
        return matchesSuffixe(scriptLocation) ? findResource(scriptLocation).exists() : false;
    }

	private Resource findResource(String scriptLocation) {
		Resource resource = null;
		if (scriptLocation.matches("\b(https?|file)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|]")) {
			try {
				resource = new UrlResource(scriptLocation);
			} catch (MalformedURLException e) {
				logger.error(e, e);
			}
		} else if (scriptLocation.startsWith("classpath")) {
			resource = new ClassPathResource(scriptLocation.substring(scriptLocation.lastIndexOf(":") + 1));
		}
		if (resource == null) {
			resource = new FileSystemResource(mudConfig.getMudSpace() + scriptLocation);
		}

		return resource;
	}

	public MudObject getObject(String handle) {
		return (MudObject) synchronizedMap.get(handle);
	}

	public boolean contains(String handle) {
		return synchronizedMap.containsKey(handle);
	}

    public void preLoadDomains() {
    }

    public void removeObject(String handle) {
		synchronizedMap.remove(handle);
	}

	/**
	 * loads one domain with a given script name
	 * @param scriptLocation the script location
	 */
	public void loadDomain(String scriptLocation) {
		Bindings binding = getBindingDelegate().createJSR223ScriptEngineBindings();
		try {
			if (logger.isInfoEnabled()) {
				logger.info("loading script:" + scriptLocation);
			}
			String extension = scriptLocation.substring(scriptLocation.lastIndexOf('.') + 1);
			Resource resource = findResource(mudConfig.getMudSpace() + scriptLocation);
			Object obj = new ScriptEngineManager().getEngineByExtension(extension).eval(new InputStreamReader(resource.getInputStream()), binding);
			if (obj instanceof Map) {
				synchronizedMap.putAll((Map<? extends String,? extends Object>) obj);
			}

		} catch (ScriptException e) {
			logger.error("Script exception loading " + scriptLocation, e);
		} catch (MalformedURLException e) {
			logger.error("MalformedUrl Exception loading " + scriptLocation, e);
		} catch (IOException e) {
			logger.error("IOException loading " + scriptLocation, e);
		}

	}

	


}
